﻿Class RotatedRectangle

    Structure ResolutionData
        Dim Axis_Overlap As Single
        Dim Axis_smallest As Vector2

    End Structure

    Public m_Axis_Overlap As Double  '// really large value
    Public m_Axis_smallest As Vector2 'New List(Of Vector2)

    Public CollisionRectangle As Rectangle

    Public Rotation As Single

    Public Origin As Vector2

    Public Sub New(ByVal theRectangle As Rectangle, ByVal theInitialRotation As Single)
        MyBase.New()
        CollisionRectangle = theRectangle
        Rotation = theInitialRotation
        'Calculate the Rectangles origin. We assume the center of the Rectangle will
        'be the point that we will be rotating around and we use that for the origin
        Origin = New Vector2((CType(theRectangle.Width, Integer) / 2), (CType(theRectangle.Height, Integer) / 2))
    End Sub

    ''' <summary>
    ''' This intersects method can be used to check a standard XNA framework Rectangle
    ''' object and see if it collides with a Rotated Rectangle object
    ''' </summary>
    ''' <param name="theRectangle"></param>
    ''' <returns></returns>
    Public Overloads Function Intersects(ByVal theRectangle As Rectangle) As Boolean
        Return Intersects(New RotatedRectangle(theRectangle, 0.0!))
    End Function




    ''' <summary>
    ''' Check to see if two Rotated Rectangls have collided
    ''' </summary>
    ''' <param name="theRectangle"></param>
    ''' <returns></returns>
    Public Overloads Function Intersects(ByVal theRectangle As RotatedRectangle) As Boolean
        'Calculate the Axis we will use to determine if a collision has occurred
        'Since the objects are rectangles, we only have to generate 4 Axis (2 for
        'each rectangle) since we know the other 2 on a rectangle are parallel.
        Dim aRectangleAxis As List(Of Vector2) = New List(Of Vector2)
        aRectangleAxis.Add((UpperRightCorner - UpperLeftCorner))
        aRectangleAxis.Add((UpperRightCorner - LowerRightCorner))
        aRectangleAxis.Add((theRectangle.UpperLeftCorner - theRectangle.LowerLeftCorner))
        aRectangleAxis.Add((theRectangle.UpperLeftCorner - theRectangle.UpperRightCorner))
        'Cycle through all of the Axis we need to check. If a collision does not occur
        'on ALL of the Axis, then a collision is NOT occurring. We can then exit out 
        'immediately and notify the calling function that no collision was detected. If
        'a collision DOES occur on ALL of the Axis, then there is a collision occurring
        'between the rotated rectangles. We know this to be true by the Seperating Axis Theorem
        For Each aAxis As Vector2 In aRectangleAxis
            If Not IsAxisCollision(theRectangle, aAxis) Then
                               Return False
            End If
        Next

        'There hasn't been a no-collision flag, so go ahead and solve it.
        'Apply MTV of current collision
        For Each aAxis As Vector2 In aRectangleAxis
            Dim UsefulMTV As ResolutionData = HowAxisCollision(theRectangle, aAxis)

            'aAxis += UsefulMTV.Axis_Overlap

        Next

        Return True

    End Function

    ''' <summary>
    ''' Determines if a collision has occurred on an Axis of one of the
    ''' planes parallel to the Rectangle
    ''' </summary>
    ''' <param name="theRectangle"></param>
    ''' <param name="aAxis"></param>
    ''' <returns></returns>
    Private Function IsAxisCollision(ByVal theRectangle As RotatedRectangle, ByVal aAxis As Vector2) As Boolean
        '########
        'Okay, current system does not allow me to get the axis i need to change.
        '########
        'Project the corners of the Rectangle we are checking on to the Axis and
        'get a scalar value of that project we can then use for comparison
        Dim aRectangleAScalars As List(Of Integer) = New List(Of Integer)
        aRectangleAScalars.Add(GenerateScalar(theRectangle.UpperLeftCorner, aAxis))
        aRectangleAScalars.Add(GenerateScalar(theRectangle.UpperRightCorner, aAxis))
        aRectangleAScalars.Add(GenerateScalar(theRectangle.LowerLeftCorner, aAxis))
        aRectangleAScalars.Add(GenerateScalar(theRectangle.LowerRightCorner, aAxis))
        'Project the corners of the current Rectangle on to the Axis and
        'get a scalar value of that project we can then use for comparison
        Dim aRectangleBScalars As List(Of Integer) = New List(Of Integer)
        aRectangleBScalars.Add(GenerateScalar(UpperLeftCorner, aAxis))
        aRectangleBScalars.Add(GenerateScalar(UpperRightCorner, aAxis))
        aRectangleBScalars.Add(GenerateScalar(LowerLeftCorner, aAxis))
        aRectangleBScalars.Add(GenerateScalar(LowerRightCorner, aAxis))
        'Get the Maximum and Minium Scalar values for each of the Rectangles
        Dim aRectangleAMinimum As Integer = aRectangleAScalars.Min
        Dim aRectangleAMaximum As Integer = aRectangleAScalars.Max
        Dim aRectangleBMinimum As Integer = aRectangleBScalars.Min
        Dim aRectangleBMaximum As Integer = aRectangleBScalars.Max
        'If we have overlaps between the Rectangles (i.e. Min of B is less than Max of A)
        'then we are detecting a collision between the rectangles on this Axis
        If ((aRectangleBMinimum <= aRectangleAMaximum) _
                    AndAlso (aRectangleBMaximum >= aRectangleAMaximum)) Then
            'aRectangleBMinimum = aRectangleAMaximum

            'Do collision HERE by generating a MTV (Minimum Translation Vector)
            'Use M_Overlap to record magnatude (positive and negative will indicate direction)
            'Use M_Axis_Smallest to record axis to translate.

            m_Axis_Overlap = aRectangleAMaximum
            m_Axis_smallest = aAxis


            Return True
        ElseIf ((aRectangleAMinimum <= aRectangleBMaximum) _
                    AndAlso (aRectangleAMaximum >= aRectangleBMaximum)) Then
            'aRectangleAMinimum = aRectangleBMaximum
            Return True
        End If
        Return False
    End Function

    ''' <summary>
    ''' Determines if a collision has occurred on an Axis of one of the
    ''' planes parallel to the Rectangle
    ''' </summary>
    ''' <param name="theRectangle"></param>
    ''' <param name="aAxis"></param>
    ''' <returns></returns>
    Private Function HowAxisCollision(ByVal theRectangle As RotatedRectangle, ByVal aAxis As Vector2) As ResolutionData
        'Project the corners of the Rectangle we are checking on to the Axis and
        'get a scalar value of that project we can then use for comparison
        Dim aRectangleAScalars As List(Of Integer) = New List(Of Integer)
        aRectangleAScalars.Add(GenerateScalar(theRectangle.UpperLeftCorner, aAxis))
        aRectangleAScalars.Add(GenerateScalar(theRectangle.UpperRightCorner, aAxis))
        aRectangleAScalars.Add(GenerateScalar(theRectangle.LowerLeftCorner, aAxis))
        aRectangleAScalars.Add(GenerateScalar(theRectangle.LowerRightCorner, aAxis))
        'Project the corners of the current Rectangle on to the Axis and
        'get a scalar value of that project we can then use for comparison
        Dim aRectangleBScalars As List(Of Integer) = New List(Of Integer)
        aRectangleBScalars.Add(GenerateScalar(UpperLeftCorner, aAxis))
        aRectangleBScalars.Add(GenerateScalar(UpperRightCorner, aAxis))
        aRectangleBScalars.Add(GenerateScalar(LowerLeftCorner, aAxis))
        aRectangleBScalars.Add(GenerateScalar(LowerRightCorner, aAxis))
        'Get the Maximum and Minium Scalar values for each of the Rectangles
        Dim aRectangleAMinimum As Integer = aRectangleAScalars.Min
        Dim aRectangleAMaximum As Integer = aRectangleAScalars.Max
        Dim aRectangleBMinimum As Integer = aRectangleBScalars.Min
        Dim aRectangleBMaximum As Integer = aRectangleBScalars.Max
        'If we have overlaps between the Rectangles (i.e. Min of B is less than Max of A)
        'then we are detecting a collision between the rectangles on this Axis
        If ((aRectangleBMinimum <= aRectangleAMaximum) _
                    AndAlso (aRectangleBMaximum >= aRectangleAMaximum)) Then
            'aRectangleBMinimum = aRectangleAMaximum
            m_Axis_Overlap = aRectangleBMinimum
            m_Axis_smallest = aAxis

            Dim ReturnMTV As New ResolutionData

            ReturnMTV.Axis_Overlap = m_Axis_Overlap
            ReturnMTV.Axis_smallest = m_Axis_smallest


            Return ReturnMTV
        ElseIf ((aRectangleAMinimum <= aRectangleBMaximum) _
                    AndAlso (aRectangleAMaximum >= aRectangleBMaximum)) Then
            'aRectangleAMinimum = aRectangleBMaximum
            'm_Axis_Overlap = aRectangleAMinimum
            'm_Axis_smallest = aAxis
            Dim ReturnMTV As New ResolutionData

            ReturnMTV.Axis_Overlap = m_Axis_Overlap
            ReturnMTV.Axis_smallest = m_Axis_smallest


            Return ReturnMTV
        End If

        'Error means no
        Dim ErrorMTV As New ResolutionData

        ErrorMTV.Axis_Overlap = 0
        ErrorMTV.Axis_smallest = Vector2.Zero

        Return ErrorMTV
    End Function


    ''' <summary>
    ''' Generates a scalar value that can be used to compare where corners of 
    ''' a rectangle have been projected onto a particular axis. 
    ''' </summary>
    ''' <param name="theRectangleCorner"></param>
    ''' <param name="theAxis"></param>
    ''' <returns></returns>
    Private Function GenerateScalar(ByVal theRectangleCorner As Vector2, ByVal theAxis As Vector2) As Integer
        'Using the formula for Vector projection. Take the corner being passed in
        'and project it onto the given Axis
        Dim aNumerator As Single = ((theRectangleCorner.X * theAxis.X) _
                    + (theRectangleCorner.Y * theAxis.Y))
        Dim aDenominator As Single = ((theAxis.X * theAxis.X) _
                    + (theAxis.Y * theAxis.Y))
        Dim aDivisionResult As Single = (aNumerator / aDenominator)
        Dim aCornerProjected As Vector2 = New Vector2((aDivisionResult * theAxis.X), (aDivisionResult * theAxis.Y))
        'Now that we have our projected Vector, calculate a scalar of that projection
        'that can be used to more easily do comparisons
        Dim aScalar As Single = ((theAxis.X * aCornerProjected.X) _
                    + (theAxis.Y * aCornerProjected.Y))
        Return CType(aScalar, Integer)
    End Function

    ''' <summary>
    ''' Rotate a point from a given location and adjust using the Origin we
    ''' are rotating around
    ''' </summary>
    ''' <param name="thePoint"></param>
    ''' <param name="theOrigin"></param>
    ''' <param name="theRotation"></param>
    ''' <returns></returns>
    Private Function RotatePoint(ByVal thePoint As Vector2, ByVal theOrigin As Vector2, ByVal theRotation As Single) As Vector2
        Dim aTranslatedPoint As Vector2 = New Vector2
        aTranslatedPoint.X = CType((theOrigin.X _
                    + (((thePoint.X - theOrigin.X) _
                    * Math.Cos(theRotation)) _
                    - ((thePoint.Y - theOrigin.Y) _
                    * Math.Sin(theRotation)))), Single)
        aTranslatedPoint.Y = CType((theOrigin.Y _
                    + (((thePoint.Y - theOrigin.Y) _
                    * Math.Cos(theRotation)) _
                    + ((thePoint.X - theOrigin.X) _
                    * Math.Sin(theRotation)))), Single)
        Return aTranslatedPoint
    End Function

    Public Function UpperLeftCorner() As Vector2
        Dim aUpperLeft As Vector2 = New Vector2(CollisionRectangle.Left, CollisionRectangle.Top)
        aUpperLeft = RotatePoint(aUpperLeft, (aUpperLeft + Origin), Rotation)
        Return aUpperLeft
    End Function

    Public Function UpperRightCorner() As Vector2
        Dim aUpperRight As Vector2 = New Vector2(CollisionRectangle.Right, CollisionRectangle.Top)
        aUpperRight = RotatePoint(aUpperRight, (aUpperRight + New Vector2((Origin.X * -1), Origin.Y)), Rotation)
        Return aUpperRight
    End Function

    Public Function LowerLeftCorner() As Vector2
        Dim aLowerLeft As Vector2 = New Vector2(CollisionRectangle.Left, CollisionRectangle.Bottom)
        aLowerLeft = RotatePoint(aLowerLeft, (aLowerLeft + New Vector2(Origin.X, (Origin.Y * -1))), Rotation)
        Return aLowerLeft
    End Function

    Public Function LowerRightCorner() As Vector2
        Dim aLowerRight As Vector2 = New Vector2(CollisionRectangle.Right, CollisionRectangle.Bottom)
        aLowerRight = RotatePoint(aLowerRight, (aLowerRight + New Vector2((Origin.X * -1), (Origin.Y * -1))), Rotation)
        Return aLowerRight
    End Function

    Public ReadOnly Property X As Integer
        Get
            Return CollisionRectangle.X
        End Get
    End Property

    Public ReadOnly Property Y As Integer
        Get
            Return CollisionRectangle.Y
        End Get
    End Property

    Public ReadOnly Property Width As Integer
        Get
            Return CollisionRectangle.Width
        End Get
    End Property

    Public ReadOnly Property Height As Integer
        Get
            Return CollisionRectangle.Height
        End Get
    End Property
End Class